﻿/*
    Copyright (c) 2012, Matías López
    All rights reserved.

    Redistribution and use in source and binary forms, with or without modification, are permitted 
    provided that the following conditions are met:
        *   Redistributions of source code must retain the above copyright notice, this list of 
            conditions and the following disclaimer.
        *   Redistributions in binary form must reproduce the above copyright notice, this list of 
            conditions and the following disclaimer in the documentation and/or other materials 
            provided with the distribution.
        *   Neither the name of Matías López nor the names of its contributors may be used to 
            endorse or promote products derived from this software without specific prior written 
            permission.
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

using Microsoft.WindowsAzure;

namespace mana.Data.Azure.Queues
{
    public class QueueProcessorProcessingEventArgs<T> : EventArgs
    {
        private T _item;

        public QueueProcessorProcessingEventArgs(T item)
        {
            _item = item;
        }

        public T Item 
        {
            get { return _item; }
        }
    }

    public class QueueProcessorErrorEventArgs<T> : EventArgs
    {
        private T _item;
        private Exception _ex;

        public QueueProcessorErrorEventArgs(T item, Exception ex)
        {
            _item = item;
            _ex = ex;
        }

        public T Item 
        {
            get { return _item; } 
        }

        public Exception Exception
        {
            get { return _ex; }
        }
    }

    /// <summary>
    /// Exposes operations to process a GenericQueue.
    /// </summary>
    /// <typeparam name="T">Object type that the processor (and queue) handles.</typeparam>
    public class GenericQueueProcessor<T>
    {
        #region Events

        /// <summary>
        /// Raised when an object is dequeued from the Queue to be processed.
        /// </summary>
        public event EventHandler<QueueProcessorProcessingEventArgs<T>> Processing;
        
        /// <summary>
        /// Raised when the processing process throws an error.
        /// </summary>
        public event EventHandler<QueueProcessorErrorEventArgs<T>> Error; 

        #endregion Events

        private GenericQueue<T> _queue;
        private Timer _timer;
        private int _timerInterval;
        private bool _isRunning;

        public GenericQueueProcessor(GenericQueue<T> queue)
        {
            if (queue == null)
                throw new ArgumentNullException("queue");

            _queue = queue;

            MessageVisibilityTimeout = TimeSpan.FromSeconds(30);
        }

        #region Properties 
        
        /// <summary>
        /// Gets a reference to the Generic Queue handled by this processor.
        /// </summary>
        public GenericQueue<T> Queue
        {
            get { return _queue; }
        }

        /// <summary>
        /// Gets or sets the wait interval when processing.
        /// </summary>
        public int IntervalOnProcessing { get; set; }

        /// <summary>
        /// Gets or sets the wait interval when the queue is empty.
        /// </summary>
        public int IntervalOnEmptyQueue { get; set; }

        /// <summary>
        /// Gets or sets the wait interval when an error is found while processing.
        /// </summary>
        public int IntervalOnError { get; set; }

        /// <summary>
        /// Indicates whether the processor is running or not.
        /// </summary>
        public bool IsRunning 
        {
            get { return _isRunning; }
        }

        /// <summary>
        /// Gets or sets the Queue messages' visibility timeout for dequeueing.
        /// </summary>
        public TimeSpan MessageVisibilityTimeout { get; set; }

        #endregion Properties

        #region Methods

        #region Public

        /// <summary>
        /// Starts the processing logic. By default, it specifies the following intervals: "IntervalOnProcessing = 1", "IntervalOnEmptyQueue = 60000", "IntervalOnError = 1000".
        /// </summary>
        public void Start()
        {
            this.Start(1, 60000, 1000);
        }

        /// <summary>
        /// Starts the processing logic.
        /// </summary>
        /// <param name="intervalOnProcessing">Wait interval when processing.</param>
        /// <param name="intervalOnEmptyQueue">Wait interval when the queue is empty.</param>
        /// <param name="intervalOnError">Wait interval when an error is found while processing.</param>
        public void Start(int intervalOnProcessing, int intervalOnEmptyQueue, int intervalOnError)
        {
            this.IntervalOnProcessing = intervalOnProcessing;
            this.IntervalOnEmptyQueue = intervalOnEmptyQueue;
            this.IntervalOnError = intervalOnError;

            InitializeTimer();

            _isRunning = true;
            _timer.Start();
        }

        /// <summary>
        /// Stops the processing logic.
        /// </summary>
        public void Stop()
        {
            _isRunning = false;

            if (_timer != null)
                _timer.Stop();
        }

        #endregion Public

        #region Private
        
        private void InitializeTimer()
        {
            if (_timer == null)
            {
                _timer = new Timer();
                _timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
                _timer.AutoReset = false;
            }
        }
        
        #endregion Private

        #region Protected

        protected virtual void OnProcessing(QueueProcessorProcessingEventArgs<T> e)
        {
            if (this.Processing != null)
                this.Processing(this, e);
        }

        protected virtual void OnError(QueueProcessorErrorEventArgs<T> e)
        {
            if (this.Error != null)
                this.Error(this, e);
        }

        #endregion Protected

        #endregion Methods

        #region Event Handlers

        private void timer_Elapsed(object source, ElapsedEventArgs e)
        {
            if (!_isRunning)
                return;

            T queueItem = default(T);

            try
            {
                DequeueReceipt receipt;
                queueItem = Queue.BeginDequeue(MessageVisibilityTimeout, out receipt);
                if (queueItem != null)
                {
                    this.OnProcessing(new QueueProcessorProcessingEventArgs<T>(queueItem));
                    Queue.EndDequeue(receipt);

                    _timerInterval = this.IntervalOnProcessing;
                }
                else
                {
                    _timerInterval = this.IntervalOnEmptyQueue;
                }
            }
            catch (Exception ex)
            {
                this.OnError(new QueueProcessorErrorEventArgs<T>(queueItem, ex));
                _timerInterval = this.IntervalOnError;
            }

            _timer.Interval = _timerInterval;
        }

        #endregion Event Handlers
    }
}
